// Copyright (c) 2002 Torvald Riegel. All rights reserved.
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.

package cpne;


import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.util.*;

// FIXME: thread safety ? (for example, TreeMap)
// FIXME: remove javadoc tags from simple/TreeModel methods ?


public class TokenTreeModel implements TreeModel
{
    protected Vector treeModelListeners = new Vector();
    protected SortedMap tokens;
    
    public TokenTreeModel()
        {
            tokens = new TreeMap();
        }

    /////////////////////////////////////////////////////////////////////
    // TreeModel methods
    public Object getChild(Object parent, int index)
        {
            if (parent == getRoot()) return tokens.keySet().toArray()[index];
            else if (tokens.containsKey(parent) && index == 0)
                return tokens.get(parent);
            else return null; // FIXME throw something instead ?
        }
    /**
     * Returns the root node. <code>this</code> in this model.
     */
    public Object getRoot()
        { return this; }
    public int getChildCount(Object parent)
        {
            if (parent == getRoot()) return tokens.size();
            else return isLeaf(parent) ? 0 : 1;
        }
    public int getIndexOfChild(Object parent, Object child)
        {
            if (parent == getRoot()) return tokens.headMap(child).size();
            else return 0;
        }
    public boolean isLeaf(Object node)
        {
            if (node == getRoot()) return tokens.size() == 0;
            else return !(node instanceof Integer);
        }
    /**
     * Returns the container object for a given index.
     *
     * It seems like sometimes another equal object is not sufficient.
     */
    public Integer getTokenIndexContainer(int index)
        {
            SortedMap tm = tokens.tailMap(new Integer(index));
            if (tm.size() == 0) return null;
            return (Integer)tm.firstKey();
        }
    
    /////////////////////////////////////////////////////////////////////
    // custom accessors
    /**
     * Adds a token at the first unused index.
     */
    public int addToken(PerlExprDocument token)
        {
            int index = findUnusedIndex(0, 1);
            setToken(index, token);
            return index;
        }
    /**
     * Adds a token of value 1 at the first unused index.
     */
    public int addToken()
        {
            return addToken(new PerlExprDocument("1"));
        }
    /**
     * Removes the token at the given index.
     */
    public void removeToken(int index)
        {
            Integer i = new Integer(index);
            if (tokens.containsKey(i)) 
            {
                // FIXME: is it really necessary to get the real object or
                // is an equal one sufficient ?
                int tmIndex = getIndexOfChild(getRoot(), i);
                Integer tmObject = getTokenIndexContainer(index);
                tokens.remove(i);
                // listener notification
                Iterator it = treeModelListeners.iterator();
                TreeModelEvent e =
                    new TreeModelEvent(this,
                                       new Object[] { this },
                                       new int[] { tmIndex },
                                       new Object[] { tmObject });
                while (it.hasNext())
                {
                    ((TreeModelListener)it.next()).treeNodesRemoved(e);
                }
            }
        }
    /**
     * Gets a token at the given index.
     */
    public PerlExprDocument getToken(int index)
        {
            if (!tokens.containsKey(new Integer(index))) return null;
            return (PerlExprDocument)tokens.get(new Integer(index));
        }
    /**
     * Sets the token at the given index, existing tokens are removed before
     * that.
     */
    public void setToken(int index, PerlExprDocument token)
        {
            removeToken(index);
            // new token
            Integer indexObj = new Integer(index);
            tokens.put(indexObj, token);
            // listener notification
            Iterator it = treeModelListeners.iterator();
            TreeModelEvent e =
                new TreeModelEvent(this,
                                   new Object[] { this },
                                   new int[] { getIndexOfChild(getRoot(),
                                                               indexObj) },
                                   new Object[] { indexObj });
            while (it.hasNext())
            {
                ((TreeModelListener)it.next()).treeNodesInserted(e);
            }
        }
    
    /**
     * Tries to find an unused index for a token.
     */
    protected int findUnusedIndex(int start, int delta)
        {
            while (start>=0 && tokens.containsKey(new Integer(start)))
            { start+=delta; }
            return start;
        }
    /**
     * Moves the token at the given index to the next free position,
     * if possible.
     */
    public int move(int index, int delta)
        {
            int newIndex = findUnusedIndex(index, delta);
            if (newIndex == -1) return index;
            PerlExprDocument token = getToken(index);
            removeToken(index);
            setToken(newIndex, token);
            return newIndex;
        }
    
    /////////////////////////////////////////////////////////////////////
    // node -> index wrappers
    protected int getTokenIndex(Object node)
        {
            if (isLeaf(node) && node!=getRoot()) 
            {
                // get the first key pointing to the node
                Iterator it = tokens.keySet().iterator();
                for (int index = 0 ; it.hasNext() ; index++)
                {
                    if (it.next() == node) return index;
                }
            }
            else if (tokens.containsKey(node)) 
            {
                return ((Integer)node).intValue();
            }
            return -1;
        }
    public void removeToken(Object node)
        {
            removeToken(getTokenIndex(node));
        }
    public int move(Object node, int delta)
        {
            int index = getTokenIndex(node);
            if (index != -1) index = move(index,delta);
            return index;
        }
    
    /////////////////////////////////////////////////////////////////////
    /**
     *
     */
    public void valueForPathChanged(TreePath path, Object newValue)
        {
            // TODO
            System.out.println("TODO: valueForPathChanged !");
        }

    /////////////////////////////////////////////////////////////////////
    // TreeModelEvent related methods
    /**
     * Adds a listener.
     */
    public void addTreeModelListener(TreeModelListener l)
        {
            treeModelListeners.add(l);
        }
    /**
     * Removes a listener.
     */
    public void removeTreeModelListener(TreeModelListener l)
        {
            treeModelListeners.remove(l);
        }
}

